feature(branch_sweep): sweep the rest of the organism from the Brain - #280
Merged
Conversation
Mind and Brain now sweep themselves, but a repo's GITHUB_TOKEN reaches only that repo — so covering the other 18 in Scope would mean 18 copies of the same workflow, drifting apart the moment one is edited. PAT_PYAUTOLABS already exists for cross-repo work (spawn_drift.yml, arxiv_papers.yml), so the sweep is written once here and pointed at each repo in turn. The gate is structural, not procedural. Deleting branches across 18 repos on one click is not something a report should be able to talk anyone into: - `mode=delete` REFUSES to run without an explicit `repos` list. There is no delete-everything form of this workflow; the all-repos path is audit-only. - A target outside branch_sweep_set.txt fails the whole run — including a run that also names valid repos. Fail closed, not partially. - The scheduled run is audit-only regardless of input. Why that is stricter here than for Mind/Brain: those are solo agent repos. PyAutoFit, PyAutoArray, PyAutoGalaxy, PyAutoLens and the workspaces take pull requests from outside contributors, and the per-repo skill's protection for that case — never enumerate origin-only collaborator branches — cannot hold in a workflow, where every branch it sees IS origin-only. A human reading the audit per repo is the substitute, so the mechanism makes that step unskippable rather than trusting a runbook. (Fork PRs are unaffected; their heads live in the fork.) branch_sweep_set.txt is policy and so lives in the Brain, not in repos.yaml — the body map's own header says identity there, per-organ policy with the organ. It carries its exclusions with reasons: the skill's two Never-touched repos, the assistants and publication surfaces, a different owner whose PAT scope is unverified, and Mind/Brain themselves, which would otherwise end up with two sweepers on different credentials. Tests pin the boundary rather than the happy path: the Never-touched and self-sweeping exclusions each fail a test if someone adds them back, and every entry is checked against the body map so a typo cannot surface as a mid-sweep clone failure. The gate's eight cases (including valid-plus-forbidden) were exercised directly against the set file before this landed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KwqicJpMqmcT5RVbyNwdKq
CI rejected the first version, and it was right to. The tenant firewall keeps instance facts — satellite repo names, GitHub owners — out of the framework organs, so they stay adoptable as a config-diff fork. A file listing the organism's repos is exactly that leak. Worse, the first version had smuggled it past: the firewall scans only .py and .sh, so a .txt list was not clean, only invisible. That is a hole to close, not a technique to keep. The hygiene conductor had already solved this, and said so in the allowlist entry it deleted — "derives its repo sets from the body map, so it names no instance fact at all". Same answer here. Policy is expressed as CATEGORIES, which every organism has; which repos fill them is whatever that organism's body map says. Both exclusions now fall out of the categories instead of being named twice: the skill's two Never-touched repos live in categories this does not sweep, so no rule has to know them. The Mind and the Brain drop out by organ ROLE, not name — each hosts its own branch_sweep.yml, and sweeping them centrally too would put two sweepers on one repo with different credentials. Roles are generic; an adopting fork has a Mind and a Brain whatever it calls them. The derived set is 25 repos where the hand-list was 18. The difference is not scope creep but the absence of arbitrariness: the hand-list had quietly omitted repos indistinguishable from the ones it included. A principled boundary includes them; the audit-first gate is what keeps that safe. Tests use a synthetic body map throughout. Necessary — this file is .py under an organ, so the firewall scans it and a fixture naming a real repo would be the same leak — but also better: it pins the contract (categories in, slugs out, unknown category fails closed) rather than today's roster. Verified the way the first version was not: `repos_sync.py --check` in full, locally, all twelve checks green, rather than assuming pytest was the gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KwqicJpMqmcT5RVbyNwdKq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why central rather than 25 copies
Mind and Brain now sweep themselves, but a repo's
GITHUB_TOKENreaches only that repo — so covering the rest of the organism's development repos would mean 25 copies of the same workflow, drifting apart the moment one is edited.PAT_PYAUTOLABSalready exists for exactly this kind of cross-repo work, so the sweep is written once here and pointed at each repo in turn.Targets are derived, never listed
The first version of this PR carried a file listing the repos. CI rejected it, and was right to. The tenant firewall keeps instance facts — satellite repo names, GitHub owners — out of the framework organs so they stay adoptable as a config-diff fork, and a hardcoded list of slugs is exactly that leak.
Worse, it had smuggled it past: the firewall scans only
.pyand.sh, so a.txtlist was not clean, only invisible. A hole to close, not a technique to keep.The hygiene conductor had already solved this, and said so in the allowlist entry it deleted — "derives its repo sets from the body map, so it names no instance fact at all." Same answer here.
bin/branch_sweep_targets.pyexpresses policy as categories (organ, library, workspace, workspace_test, workspace_developer, howto) and reads which repos fill them fromrepos.yaml.Both exclusions now fall out of the categories rather than being named twice:
branch_sweep.yml, and sweeping them centrally too would put two sweepers on one repo with different credentials. Roles are generic; an adopting fork has a Mind and a Brain whatever it calls them.The derived set is 25 repos where the hand-list was 18. Not scope creep — the hand-list had quietly omitted repos indistinguishable from ones it included. A principled boundary includes them; the audit-first gate below is what keeps that safe.
The gate is structural, not procedural
mode=deleterefuses to run without an explicitreposlist. There is no delete-everything form of this workflow; the all-repos path is audit-only.Why stricter here than for Mind/Brain
Those are solo agent repos. The libraries and workspaces take pull requests from outside contributors, and the per-repo skill's protection for that case — "never enumerate origin-only collaborator branches" — cannot hold in a workflow, where every branch it sees is origin-only. A human reading the audit per repo is the substitute, so the mechanism makes that step unskippable rather than trusting a runbook. (Fork PRs are unaffected — their heads live in the fork.)
Checks
pytest tests/→ 507 passed ·check_skill_line_counts.sh→ OK ·repos_sync.py --checkin full → all 12 checks green, including the tenant firewall.That last one is the lesson from the first round: I ran pytest and called it verified, but pytest was never the failing gate —
repos_sync.pyis a separate CI step I hadn't run locally.Tests use a synthetic body map throughout. Necessary (this file is
.pyunder an organ, so a fixture naming a real repo would be the same leak), but also better: it pins the contract — categories in, slugs out, unknown category fails closed — rather than today's roster.The delete gate was exercised directly against the real derived set, 8 cases:
audit, no listdelete, no listdelete, one valid repodelete, two comma-separateddelete, a Never-touched repodelete, a repo not in the setdelete, valid + forbidden togetherdelete, a self-sweeping repo🤖 Generated with Claude Code
https://claude.ai/code/session_01KwqicJpMqmcT5RVbyNwdKq